namespace Resharper.Abstractions.Actions
{
  using System;
  using System.Diagnostics;
  using System.Linq;
  using JetBrains.Annotations;
  using JetBrains.ProjectModel;
  using JetBrains.ReSharper.Feature.Services.Bulbs;
  using JetBrains.ReSharper.Psi;
  using JetBrains.ReSharper.Psi.CSharp;
  using JetBrains.ReSharper.Psi.Tree;
  using JetBrains.ReSharper.Psi.Util;
  using JetBrains.TextControl;
  using Resharper.Abstractions.Solutions;

  /// <summary>Defines the <see cref="ExecuteContext"/> class.</summary>
  public class ExecuteContext : ProviderContext
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="ExecuteContext"/> class.</summary>
    /// <param name="provider">The provider.</param>
    /// <param name="solution">The solution.</param>
    /// <param name="tag">The tag.</param>
    internal ExecuteContext([NotNull] IContextActionDataProvider provider, [NotNull] ISolution solution, [CanBeNull] object tag) : base(provider)
    {
      if (provider == null)
      {
        throw new ArgumentNullException("provider");
      }

      if (solution == null)
      {
        throw new ArgumentNullException("solution");
      }

      this.Solution = new Solution(solution);
      this.Tag = tag;
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets the factory.
    /// </summary>
    [NotNull]
    public Factory Factory
    {
      get
      {
        var psiModule = this.GetPsiModule();
        Debug.Assert(psiModule != null, "psiModule != null");

        var factory = CSharpElementFactory.GetInstance(psiModule);
        Debug.Assert(factory != null, "factory != null");

        return new Factory(factory);
      }
    }

    /// <summary>
    /// Gets the formatter.
    /// </summary>
    public CodeFormatter Formatter
    {
      get
      {
        var psiModule = this.GetPsiModule();
        Debug.Assert(psiModule != null, "psiModule != null");

        var languageType = psiModule.PsiLanguage;
        Debug.Assert(languageType != null, "languageType != null");

        return new CodeFormatter(this.Solution, languageType);
      }
    }

    /// <summary>
    /// Gets the solution.
    /// </summary>
    [NotNull]
    public Solution Solution { get; private set; }

    /// <summary>
    /// Gets or sets the tag.
    /// </summary>
    /// <value>The tag.</value>
    public object Tag { get; set; }

    /// <summary>
    /// Gets or sets the internal text control.
    /// </summary>
    /// <value>The internal text control.</value>
    [CanBeNull]
    internal ITextControl InternalTextControl { get; set; }

    #endregion

    #region Methods

    /// <summary>Gets the name of the type element by CLR.</summary>
    /// <param name="typeName">Name of the type.</param>
    /// <returns>Returns the type element by CLR name.</returns>
    [CanBeNull]
    internal ITypeDeclaration GetTypeByName([NotNull] string typeName)
    {
      if (typeName == null)
      {
        throw new ArgumentNullException("typeName");
      }

      Debug.Assert(this.Solution != null, "solution != null");

      var psiModule = this.GetPsiModule();
      if (psiModule == null)
      {
        return null;
      }

      var typeElement = TypeElementUtil.GetTypeElementByClrName(new ClrTypeName(typeName), psiModule);
      if (typeElement == null)
      {
        return null;
      }

      return typeElement.GetDeclarations().FirstOrDefault() as ITypeDeclaration;
    }

    /// <summary>Gets the psi module.</summary>
    /// <returns>Returns the psi module.</returns>
    private IPsiModule GetPsiModule()
    {
      return this.InternalProvider.Document.GetPsiSourceFile(this.Solution.InternalSolution).GetPsiModule();
    }

    #endregion
  }
}